# 16. 面试问题

# 1. css文件放在首部还是底部

  • CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。如果不阻塞DOM渲染用户体验可谓极差,可能会渲染多次,而且渲染是有成本的。因此,基于性能与用户体验的考虑,浏览器会尽量减少渲染的次数,CSS顺理成章地阻塞页面渲染。
  • JS 阻塞 DOM 解析,但浏览器会"偷看"DOM,预先下载相关资源。
  • 浏览器遇到 <script>且没有deferasync属性的标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。因为js脚本中可能会获取计算样式,需要等待css加载后执行。defer被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。

所以,你现在明白为何<script>最好放底部,<link>最好放头部,如果头部同时有<script><link>的情况下,最好将<script>放在<link>上面了吗?

放在底部可能会造成以下问题:

  • 可能造成js计算样式不对
  • 阻塞DOM解析

# 提高性能的九个技巧

第一条,DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
第二条,如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。
第三条,不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// good 
el.className += " theclassname";

// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
1
2
3
4
5
6
7
8
9
10
11

第四条,尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
第五条,先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
第六条,position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
第七条,只在必要的时候,才将元素的display属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
第八条,使用虚拟DOM的脚本库,比如React等。
第九条,使用 window.requestAnimationFrame() 这两个方法调节重新渲染。

https://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html (opens new window) https://juejin.im/post/59c60691518825396f4f71a1#heading-2 (opens new window)

# 2. translate会造成重绘么?

left/top/margin 之类的属性会影响到元素在文档中的布局,当对布局(layout)进行动画时,该元素的布局改变可能会影响到其他元素在文档中的位置,就导致了所有被影响到的元素都要进行重新布局,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。

transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。

层创立的条件如下:

  • 3D 或透视变换 CSS 属性
  • 使用加速视频解码的 <video>
  • 元素拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 复合插件(如 Flash)进行 opacity/transform 动画的元素
  • 拥有加速 CSS filters 的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

# 总结

  • 对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到 GPU 中
  • 对合成属性进行动画,浏览器会为元素创建一个独立的复合层,当元素内容没有发生改变,该层就不会被重绘,浏览器会通过重新复合来创建动画帧。

链接:https://www.zhihu.com/question/33629083/answer/57062375 (opens new window)

# 3. 为什么要减少HTTP请求

好处:

  • 减少http请求头的数据量
  • 减少http连接的开销
  • 会显著增加浏览器和服务器的网络资源消耗

坏处:

  • 多个请求放到一个请求中,只要TCP丢了一个包,TCP自然会重传,需要T1时间,就会造成页面加载时间增加T1
  • 缓存失效率就越高,浏览器缓存利用率相对偏低。

推荐2-6个合并

https://blog.csdn.net/chenchun91/article/details/52207008 (opens new window)

# 4. 垃圾回收算法优缺点

# 引用计数法

假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计算器的值 为0,就说明对象A没有引用了,可以被回收。

优点 : 1、可即刻回收垃圾。 2、在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember错误。
3、区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

缺点 :
1、每次对象被引用时,都需要去更新计数器,有一点时间开销。
2、浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
3、无法解决循环引用问题。(最大的缺点)

# 标记清除算法

将垃圾回收分为2个阶段,分别是标记和清除。

标记 :从根节点开始标记引用的对象。
清除 :未被标记引用的对象就是垃圾对象,可以被清理。

优点
标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。

缺点:
1、效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用 而言这个体验是非常差的。
2、通过标记清除算法清理出来的内容,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

# 你觉得你在开发中遇到的最难的点是什么?如何解决的。

S:Situation 当时做这项工作或项目是什么背景? 因为之前只有一套测试环境,多人开发不同需求在一套环境中不够用,所以需要将一套测试环境变为多套,以便不同需求在不同测试环境中开发测试,解除多个需求在一套测试环境中耦合的情况。

T:Task 当时你接到的任务是什么,尽可能详细描述。 实现工单管理项目测试环境的多set的建立,就是说不同的主号可以设置不同的set环境,比如set1~10其中一个,以便开发不同需求可以使用不同set环境。

A:Action 你是怎么思考的,采取了什么策略和行动,这一步很能体现你处理问题的能力、逻辑思维、沟通和策略思考。 接到任务后,分析如何实现,其实就是打包部署时将前端资源存放在不同的set文件夹下,访问页面获取资源时根据主号的不同set从文件夹中获取。
部署时我们是使用coding流水线部署的,所以需要修改流水线的配置,启动时传两个数字参数env和set,env表示环境,set表示set几,执行流水线时会选择不同的env和set,往cos发送资源时会多拼一层路径set_2_8,这样资源就存在服务器不同的set文件夹下了,其实就是比之前多一层文件夹。
然后获取时,首先我们项目是需要先登录的,登录后可以从cookie中获取到主号,访问资源时在nginx中匹配到对应的资源路径,然后查找cookie中是否存在qidian_env_set,如果存在直接拼接在路径上,获取到对应set的资源,否则使用lua脚本调接口获取到对应的env和set,拼接到路径上,并保存在cookie中。
这样就实现了多套测试环境。

R:Review 事情结束以后你有什么收获和反思,旨在看你是不是一个会反思总结的人,反思总结能力在职场上非常重要。 做完之后学会了lua脚本语言以及在nginx的应用。反思:虽然实现了多set,但是之后还是会遇到很多问题,比如查找不同set的账号很困难,以及不同set有哪些需求正在使用。之后也需要做好账号和需求在不同set的记录工作,防止后面更加混乱。

# 最难的项目是什么?

保定地质灾害地图系统,实现了携带北斗导航的人车飞机轮船在地图上实时位置的展示以及历史轨迹的展示,接收报警功能,保障了出野外人员的安全。
难点是临时接手,也没文档,是angularjs实现的,之前了解过一些但是没实战过,所以挑战很大。还有代码都是写在一个js文件中的,很难阅读,并且不同业务功能都是混合在一起,导致很难查看和修改。
语言问题通过结合业务代码查找文档,不会的地方通过百度、问同事基本两天就可以修改功能了。然后就是重构代码,先将公共组件、公共函数、css、图片等存放在不同的文件夹中,然后将不同业务代码集中在一块,解决业务之间的耦合,做好注释,每块业务代码保存在不同的js中,后面哪块功能遇到问题只需要修改对应的js就行。

# 面试经历

https://github.com/mybells/vue-test-study/blob/master/html/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E5%8F%AF%E8%83%BD%E4%BC%9A%E9%97%AE%E5%88%B0%EF%BC%9F.md (opens new window)